/*-------------------------------------------------------------------------
 *
 * htup_details.h
 *	  POSTGRES 堆元组头部定义。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/access/htup_details.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef HTUP_DETAILS_H
#define HTUP_DETAILS_H

#include "access/htup.h"
#include "access/transam.h"
#include "access/tupdesc.h"
#include "access/tupmacs.h"
#include "storage/bufpage.h"

/*
 * MaxTupleAttributeNumber 限制元组中（用户）列的数量。
 * 该值的关键限制是元组的固定开销大小加上 null 值位图的大小（每列 1 位），
 * 再加上 MAXALIGN 对齐，必须能够适应 t_hoff，t_hoff 是 uint8。在大多数
 * 机器上，若不使 t_hoff 更宽，上限略高于 1700。我们在这里和对 MaxHeapAttributeNumber
 * 使用整数字，以便 HeapTupleHeaderData 布局的更改不会改变支持的最大列数。
 */
#define MaxTupleAttributeNumber 1664	/* 8 * 208 */

/*
 * MaxHeapAttributeNumber 限制表中（用户）列的数量。
 * 这应该比 MaxTupleAttributeNumber 小一些。它必须至少小一，否则我们将在最大宽度的
 * 表上执行 UPDATE 时失败（因为 UPDATE 必须形成包含 CTID 的工作元组）。
 * 实际上，我们希望有一些额外的空间，以便优雅地支持添加隐藏的“resjunk”列的操作，
 * 例如 SELECT * FROM wide_table ORDER BY foo, bar, baz。
 * 无论如何，根据列数据类型，如果您有超过一千列，您可能会遇到基于磁盘块的整体元组大小限制。
 * TOAST 不会提供帮助。
 */
#define MaxHeapAttributeNumber	1600	/* 8 * 200 */


/*
 * 堆元组头。为了避免浪费空间，字段应该以避免结构填充的方式布局。
 *
 * 组合类型（行类型）的数据共享与磁盘元组相同的一般结构，以便可以使用相同的例程来构建和检查它们。然而，要求稍有不同：一个 Datum 不需要任何事务可见性信息，并且确实需要一个长度字和一些嵌入的类型信息。我们可以通过将堆元组的 xmin/cmin/xmax/cmax/xvac 字段覆盖为 Datum 所需的字段来实现这一点。通常，所有在内存中构建的元组将使用 Datum 字段进行初始化；但当一个元组将要插入到表中时，事务字段将被填充，覆盖数据字段。
 *
 * 堆元组的整体结构如下：
 *			固定字段（HeapTupleHeaderData 结构）
 *			nulls 位图（如果 t_infomask 中设置了 HEAP_HASNULL）
 *			对齐填充（根据需要使用户数据达到 MAXALIGN）
 *			对象 ID（如果 t_infomask 中设置了 HEAP_HASOID_OLD，不再创建）
 *			用户数据字段
 *
 * 我们在三个物理字段中存储五个“虚拟”字段 Xmin、Cmin、Xmax、Cmax 和 Xvac。Xmin 和 Xmax 始终被真实存储，但 Cmin、Cmax 和 Xvac 共享一个字段。这是可行的，因为我们知道 Cmin 和 Cmax 仅在插入和删除事务的生命周期内是有趣的。如果一个元组在同一事务中被插入和删除，我们存储一个“组合”命令 ID，该 ID 可映射到真实的 cmin 和 cmax，但仅通过原始后端的局部状态。有关更多详细信息，请参见 combocid.c。与此同时，Xvac 仅由旧式 VACUUM FULL 设置，该操作没有任何命令子结构，因此无需 Cmin 或 Cmax。（这要求旧式 VACUUM FULL 从不尝试移动 Cmin 或 Cmax 仍然有意义的元组，即，正在进行插入或删除的元组。）
 *
 * 关于 t_ctid 的一段说明：每当一个新的元组存储在磁盘上时，其 t_ctid 就用自己的 TID（位置）初始化。如果这个元组被更新，它的 t_ctid 会更改为指向替代版本的元组。或者，如果元组由于分区键的更新而从一个分区移动到另一个分区，t_ctid 会设置为一个特殊值以指示这一点（见 ItemPointerSetMovedPartitions）。因此，一个元组是其行的最新版本，当且仅当 XMAX 无效或 t_ctid 指向自身（在这种情况下，如果 XMAX 有效，该元组要么被锁定，要么被删除）。可以跟随 t_ctid 链接找到该行的最新版本，除非它被移动到不同的分区。然而要注意，VACUUM 可能会在擦除指向的（更新的）元组之前擦除指向的（旧的）元组。因此，在跟随 t_ctid 链接时，有必要检查被引用的槽是否为空，或者是否包含一个不相关的元组。检查被引用的元组的 XMIN 是否等于引用元组的 XMAX 以验证它实际上是后代版本，而不是最近被 VACUUM 清理的槽中存储的不相关元组。如果任一检查失败，可以假设没有活的后代版本。
 *
 * 有时 t_ctid 被用来存储一个投机性插入标记，而不是一个真实的 TID。投机性标记是在正在插入的元组上设置的，直到插入者确认它希望继续进行插入。因此，投机性标记应只出现在 XMAX 仍在进行中或无效/中止的元组上。当插入确认后，标记会被元组的真实 TID 替换。在跟随 t_ctid 链接时，绝不应看到投机性插入标记，因为它们不在更新上使用，仅在插入上使用。
 *
 * 在固定头字段之后，存储了 nulls 位图（从 t_bits 开始）。如果 t_infomask 显示元组中没有 null，则 **不** 存储位图。如果存在 OID 字段（如 t_infomask 所示），则它在用户数据之前存储，用户数据从 t_hoff 显示的偏移量开始。注意，t_hoff 必须是 MAXALIGN 的倍数。
 */

typedef struct HeapTupleFields
{
	TransactionId t_xmin;		/* 插入 xact ID */
	TransactionId t_xmax;		/* 删除或锁定 xact ID */

	union
	{
		CommandId	t_cid;		/* 插入或删除命令 ID，或两者都有 */
		TransactionId t_xvac;	/* 旧式 VACUUM FULL xact ID */
	}			t_field3;
} HeapTupleFields;

typedef struct DatumTupleFields
{
	int32		datum_len_;		/* varlena 头部（请勿直接触摸！） */

	int32		datum_typmod;	/* -1，或记录类型的标识符 */

	Oid			datum_typeid;	/* 复合类型 OID，或 RECORDOID */

	/*
	 * datum_typeid 不能是对复合类型的域，只能是普通复合类型，即使该 datum 被认为是域-复合类型的值。
	 * 这与 CoerceToDomain 不改变基本类型值的物理表示的一般原则是一致的。
	 *
	 * 注意：字段顺序的选择考虑到 Oid 可能在某天扩展到 64 位。
	 */
} DatumTupleFields;

struct HeapTupleHeaderData
{
	union
	{
		HeapTupleFields t_heap;
		DatumTupleFields t_datum;
	}			t_choice;

	ItemPointerData t_ctid;		
/* 当前的 TID 或更近的元组 (或一个
                                   * 投机插入令牌) */

	/* 以下字段必须匹配 MinimalTupleData！ */

#define FIELDNO_HEAPTUPLEHEADERDATA_INFOMASK2 2
	uint16		t_infomask2;	/* 属性数量 + 各种标志 */

#define FIELDNO_HEAPTUPLEHEADERDATA_INFOMASK 3
	uint16		t_infomask;		/* 各种标志位，见下文 */

#define FIELDNO_HEAPTUPLEHEADERDATA_HOFF 4
	uint8		t_hoff;			/* 包含位图和填充的头部大小 */

	/* ^ - 23 字节 - ^ */

#define FIELDNO_HEAPTUPLEHEADERDATA_BITS 5
	bits8		t_bits[FLEXIBLE_ARRAY_MEMBER];	/* NULLs 的位图 */

	/* 结构结尾还跟随更多数据 */
};

/* typedef 出现在 htup.h 中 */

#define SizeofHeapTupleHeader offsetof(HeapTupleHeaderData, t_bits)

/*
 * 存储在 t_infomask 中的信息：
 */
#define HEAP_HASNULL			0x0001	/* 具有空属性 */
#define HEAP_HASVARWIDTH		0x0002	/* 具有可变宽度属性 */
#define HEAP_HASEXTERNAL		0x0004	/* 具有外部存储属性 */
#define HEAP_HASOID_OLD			0x0008	/* 具有对象 ID 字段 */
#define HEAP_XMAX_KEYSHR_LOCK	0x0010	/* xmax 是一个关键共享锁 */
#define HEAP_COMBOCID			0x0020	/* t_cid 是一个组合 CID */
#define HEAP_XMAX_EXCL_LOCK		0x0040	/* xmax 是独占锁 */
#define HEAP_XMAX_LOCK_ONLY		0x0080	/* xmax（如果有效）只是一个锁 */

 /* xmax 是共享锁 */
#define HEAP_XMAX_SHR_LOCK	(HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)

#define HEAP_LOCK_MASK	(HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | \
						 HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_XMIN_COMMITTED		0x0100	/* t_xmin 已提交 */
#define HEAP_XMIN_INVALID		0x0200	/* t_xmin 无效/已中止 */
#define HEAP_XMIN_FROZEN		(HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
#define HEAP_XMAX_COMMITTED		0x0400	/* t_xmax 已提交 */
#define HEAP_XMAX_INVALID		0x0800	/* t_xmax 无效/已中止 */
#define HEAP_XMAX_IS_MULTI		0x1000	/* t_xmax 是 MultiXactId */
#define HEAP_UPDATED			0x2000	/* 这是行的 UPDATEd 版本 */
#define HEAP_MOVED_OFF			0x4000	/* 由 9.0 之前的 VACUUM FULL 移动到另一个地方；为了二进制升级支持而保留 */
#define HEAP_MOVED_IN			0x8000	/* 由 9.0 之前的 VACUUM FULL 从另一个地方移动；为了二进制升级支持而保留 */
#define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)

#define HEAP_XACT_MASK			0xFFF0	/* 与可见性相关的位 */

/*
 * 只有当设置了 HEAP_XMAX_LOCK_ONLY 位时，元组才被锁定（即不被其 Xmax 更新）；或者，为了 pg_upgrade 的原因，如果 Xmax 不是多重且设置了 EXCL_LOCK 位。
 *
 * 另请参阅 HeapTupleHeaderIsOnlyLocked，它还检查可能的已中止更新事务。
 *
 * 注意，参数的多次评估。
 */
#define HEAP_XMAX_IS_LOCKED_ONLY(infomask) \
	(((infomask) & HEAP_XMAX_LOCK_ONLY) || \
	 (((infomask) & (HEAP_XMAX_IS_MULTI | HEAP_LOCK_MASK)) == HEAP_XMAX_EXCL_LOCK))

/*
 * 一个具有 HEAP_XMAX_IS_MULTI 和 HEAP_XMAX_LOCK_ONLY，但没有 HEAP_XMAX_EXCL_LOCK 和 HEAP_XMAX_KEYSHR_LOCK 的元组必须来自于一个在 9.2 或更早版本中被共享锁锁定的元组，然后进行了 pg_upgrade。
 *
 * 在 9.2 及之前，当该元组有多个 FOR SHARE 锁时，仅设置 HEAP_XMAX_IS_MULTI。那时，它设置了 HEAP_XMAX_LOCK_ONLY（以前的名称不同），但没有设置 HEAP_XMAX_EXCL_LOCK 和 HEAP_XMAX_KEYSHR_LOCK。这个组合在 9.3 及之后不再可能，因此如果我们看到这个组合，我们可以确定该元组是在更早的版本中被锁定的；由于所有这些锁都已经消失（它们不能通过 pg_upgrade 存活），因此可以安全地认为这些元组没有被锁定。
 *
 * 我们不能在本地解析这种多重事务，因为结果将是虚假的，无论它们在当前有效的多重事务范围内的状态如何。
 */
#define HEAP_LOCKED_UPGRADED(infomask) \
( \
	 ((infomask) & HEAP_XMAX_IS_MULTI) != 0 && \
	 ((infomask) & HEAP_XMAX_LOCK_ONLY) != 0 && \
	 (((infomask) & (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)) == 0) \
)

/*
 * 使用这些来测试特定锁是否应用于元组
 */
#define HEAP_XMAX_IS_SHR_LOCKED(infomask) \
	(((infomask) & HEAP_LOCK_MASK) == HEAP_XMAX_SHR_LOCK)
#define HEAP_XMAX_IS_EXCL_LOCKED(infomask) \
	(((infomask) & HEAP_LOCK_MASK) == HEAP_XMAX_EXCL_LOCK)
#define HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) \
	(((infomask) & HEAP_LOCK_MASK) == HEAP_XMAX_KEYSHR_LOCK)

/* 当 Xmax 变化时，将这些全部关闭 */
#define HEAP_XMAX_BITS (HEAP_XMAX_COMMITTED | HEAP_XMAX_INVALID | \
						HEAP_XMAX_IS_MULTI | HEAP_LOCK_MASK | HEAP_XMAX_LOCK_ONLY)

/*
 * 存储在 t_infomask2 中的信息：
 */
#define HEAP_NATTS_MASK			0x07FF	/* 11 位用于属性数量 */
/* 位 0x1800 可用 */
#define HEAP_KEYS_UPDATED		0x2000	/* 元组被更新且关键列已修改，或元组被删除 */
#define HEAP_HOT_UPDATED		0x4000	/* 元组被 HOT 更新 */
#define HEAP_ONLY_TUPLE			0x8000	/* 这是仅堆元组 */

#define HEAP2_XACT_MASK			0xE000	/* 与可见性相关的位 */

/*
 * HEAP_TUPLE_HAS_MATCH 是在哈希连接期间使用的临时标志。它只
 * 在哈希表中的元组中使用，这些元组不需要任何可见性信息，
 * 所以我们可以将其覆盖在可见性标志上，而不是使用一个专用位。
 */
#define HEAP_TUPLE_HAS_MATCH	HEAP_ONLY_TUPLE /* 元组有一个连接匹配 */

/*
 * HeapTupleHeader 访问宏
 *
 * 注意：小心对 "tup" 参数的多次评估。但 Set 宏只评估其
 * 另一个参数一次。
 */

/*
 * HeapTupleHeaderGetRawXmin 返回 "原始" xmin 字段，即最初用于插入
 * 元组的 xid。然而，元组可能实际上被冷冻（通过 HeapTupleHeaderSetXminFrozen），
 * 在这种情况下，元组的 xmin 对每个快照都是可见的。在 PostgreSQL 9.4 之前，
 * 我们实际上将 xmin 更改为 FrozenTransactionId，并且该值可能仍会在磁盘上
 * 被遇到。
 */
#define HeapTupleHeaderGetRawXmin(tup) \
( \
	(tup)->t_choice.t_heap.t_xmin \
)

#define HeapTupleHeaderGetXmin(tup) \
( \
	HeapTupleHeaderXminFrozen(tup) ? \
		FrozenTransactionId : HeapTupleHeaderGetRawXmin(tup) \
)

#define HeapTupleHeaderSetXmin(tup, xid) \
( \
	(tup)->t_choice.t_heap.t_xmin = (xid) \
)

#define HeapTupleHeaderXminCommitted(tup) \
( \
	((tup)->t_infomask & HEAP_XMIN_COMMITTED) != 0 \
)

#define HeapTupleHeaderXminInvalid(tup) \
( \
	((tup)->t_infomask & (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)) == \
		HEAP_XMIN_INVALID \
)

#define HeapTupleHeaderXminFrozen(tup) \
( \
	((tup)->t_infomask & (HEAP_XMIN_FROZEN)) == HEAP_XMIN_FROZEN \
)

#define HeapTupleHeaderSetXminCommitted(tup) \
( \
	AssertMacro(!HeapTupleHeaderXminInvalid(tup)), \
	((tup)->t_infomask |= HEAP_XMIN_COMMITTED) \
)

#define HeapTupleHeaderSetXminInvalid(tup) \
( \
	AssertMacro(!HeapTupleHeaderXminCommitted(tup)), \
	((tup)->t_infomask |= HEAP_XMIN_INVALID) \
)

#define HeapTupleHeaderSetXminFrozen(tup) \
( \
	AssertMacro(!HeapTupleHeaderXminInvalid(tup)), \
	((tup)->t_infomask |= HEAP_XMIN_FROZEN) \
)

/*
 * HeapTupleHeaderGetRawXmax 获取原始 Xmax 字段。要找出更新
 * 元组的 Xid，您可能需要解析 MultiXactId（如果设置了某些位）。
 * HeapTupleHeaderGetUpdateXid 检查这些位，并在必要时处理
 * MultiXactId。这可能涉及 multixact I/O，因此仅在绝对必要时使用。
 */
#define HeapTupleHeaderGetUpdateXid(tup) \
( \
	(!((tup)->t_infomask & HEAP_XMAX_INVALID) && \
	 ((tup)->t_infomask & HEAP_XMAX_IS_MULTI) && \
	 !((tup)->t_infomask & HEAP_XMAX_LOCK_ONLY)) ? \
		HeapTupleGetUpdateXid(tup) \
	: \
		HeapTupleHeaderGetRawXmax(tup) \
)

#define HeapTupleHeaderGetRawXmax(tup) \
( \
	(tup)->t_choice.t_heap.t_xmax \
)

#define HeapTupleHeaderSetXmax(tup, xid) \
( \
	(tup)->t_choice.t_heap.t_xmax = (xid) \
)

/*
 * HeapTupleHeaderGetRawCommandId 将提供头中的内容，无论它是否有用。
 * 大多数代码应改用 HeapTupleHeaderGetCmin 或 HeapTupleHeaderGetCmax，
 * 但请注意，这些断言您可以获得合理的结果，即您处于源事务中！
 */
#define HeapTupleHeaderGetRawCommandId(tup) \
( \
	(tup)->t_choice.t_heap.t_field3.t_cid \
)

/* SetCmin 相对简单，因为我们从不需要组合 CID */
#define HeapTupleHeaderSetCmin(tup, cid) \
do { \
	Assert(!((tup)->t_infomask & HEAP_MOVED)); \
	(tup)->t_choice.t_heap.t_field3.t_cid = (cid); \
	(tup)->t_infomask &= ~HEAP_COMBOCID; \
} while (0)

/* SetCmax 必须在 HeapTupleHeaderAdjustCmax 之后使用；请参阅 combocid.c */
#define HeapTupleHeaderSetCmax(tup, cid, iscombo) \
do { \
	Assert(!((tup)->t_infomask & HEAP_MOVED)); \
	(tup)->t_choice.t_heap.t_field3.t_cid = (cid); \
	if (iscombo) \
		(tup)->t_infomask |= HEAP_COMBOCID; \
	else \
		(tup)->t_infomask &= ~HEAP_COMBOCID; \
} while (0)

#define HeapTupleHeaderGetXvac(tup) \
( \
	((tup)->t_infomask & HEAP_MOVED) ? \
		(tup)->t_choice.t_heap.t_field3.t_xvac \
	: \
		InvalidTransactionId \
)

#define HeapTupleHeaderSetXvac(tup, xid) \
do { \
	Assert((tup)->t_infomask & HEAP_MOVED); \
	(tup)->t_choice.t_heap.t_field3.t_xvac = (xid); \
} while (0)

#define HeapTupleHeaderIsSpeculative(tup) \
( \
	(ItemPointerGetOffsetNumberNoCheck(&(tup)->t_ctid) == SpecTokenOffsetNumber) \
)

#define HeapTupleHeaderGetSpeculativeToken(tup) \
( \
	AssertMacro(HeapTupleHeaderIsSpeculative(tup)), \
	ItemPointerGetBlockNumber(&(tup)->t_ctid) \
)

#define HeapTupleHeaderSetSpeculativeToken(tup, token)	\
( \
	ItemPointerSet(&(tup)->t_ctid, token, SpecTokenOffsetNumber) \
)

#define HeapTupleHeaderIndicatesMovedPartitions(tup) \
	ItemPointerIndicatesMovedPartitions(&(tup)->t_ctid)

#define HeapTupleHeaderSetMovedPartitions(tup) \
	ItemPointerSetMovedPartitions(&(tup)->t_ctid)

#define HeapTupleHeaderGetDatumLength(tup) \
	VARSIZE(tup)

#define HeapTupleHeaderSetDatumLength(tup, len) \
	SET_VARSIZE(tup, len)

#define HeapTupleHeaderGetTypeId(tup) \
( \
	(tup)->t_choice.t_datum.datum_typeid \
)

#define HeapTupleHeaderSetTypeId(tup, typeid) \
( \
	(tup)->t_choice.t_datum.datum_typeid = (typeid) \
)

#define HeapTupleHeaderGetTypMod(tup) \
( \
	(tup)->t_choice.t_datum.datum_typmod \
)

#define HeapTupleHeaderSetTypMod(tup, typmod) \
( \
	(tup)->t_choice.t_datum.datum_typmod = (typmod) \
)

/*
 * 注意，一旦元组已知被中止或将要更新的事务已知被中止，
 * 我们就停止考虑该元组是 HOT 更新的。为了最佳效率，
 * 在使用该宏之前检查元组可见性，这样 INVALID 位将尽可能
 * 是最新的。
 */
#define HeapTupleHeaderIsHotUpdated(tup) \
( \
	((tup)->t_infomask2 & HEAP_HOT_UPDATED) != 0 && \
	((tup)->t_infomask & HEAP_XMAX_INVALID) == 0 && \
	!HeapTupleHeaderXminInvalid(tup) \
)

#define HeapTupleHeaderSetHotUpdated(tup) \
( \
	(tup)->t_infomask2 |= HEAP_HOT_UPDATED \
)

#define HeapTupleHeaderClearHotUpdated(tup) \
( \
	(tup)->t_infomask2 &= ~HEAP_HOT_UPDATED \
)

#define HeapTupleHeaderIsHeapOnly(tup) \
( \
  ((tup)->t_infomask2 & HEAP_ONLY_TUPLE) != 0 \
)

#define HeapTupleHeaderSetHeapOnly(tup) \
( \
  (tup)->t_infomask2 |= HEAP_ONLY_TUPLE \
)

#define HeapTupleHeaderClearHeapOnly(tup) \
( \
  (tup)->t_infomask2 &= ~HEAP_ONLY_TUPLE \
)

#define HeapTupleHeaderHasMatch(tup) \
( \
  ((tup)->t_infomask2 & HEAP_TUPLE_HAS_MATCH) != 0 \
)

#define HeapTupleHeaderSetMatch(tup) \
( \
  (tup)->t_infomask2 |= HEAP_TUPLE_HAS_MATCH \
)

#define HeapTupleHeaderClearMatch(tup) \
( \
  (tup)->t_infomask2 &= ~HEAP_TUPLE_HAS_MATCH \
)

#define HeapTupleHeaderGetNatts(tup) \
	((tup)->t_infomask2 & HEAP_NATTS_MASK)

#define HeapTupleHeaderSetNatts(tup, natts) \
( \
	(tup)->t_infomask2 = ((tup)->t_infomask2 & ~HEAP_NATTS_MASK) | (natts) \
)

#define HeapTupleHeaderHasExternal(tup) \
		(((tup)->t_infomask & HEAP_HASEXTERNAL) != 0)


/*
 * BITMAPLEN(NATTS) -
 *		根据数据列的数量计算空位图的大小。
 */
#define BITMAPLEN(NATTS)	(((int)(NATTS) + 7) / 8)

/*
 * MaxHeapTupleSize 是堆元组的最大允许大小，包含头和
 * MAXALIGN 对齐填充。基本上，它是 BLCKSZ 减去必须
 * 在磁盘页上的其他内容。由于堆页面不使用任何 "特殊空间"，
 * 因此没有针对该空间的扣减。
 *
 * 注意：我们允许指向元组的 ItemId，确保一个空页面
 * 确实可以容纳这样大小的元组。由于 ItemIds 和元组有
 * 不同的对齐要求，不要假设您可以在同一页面上装下 2
 * 个大小为 MaxHeapTupleSize/2 的元组。
 */
#define MaxHeapTupleSize  (BLCKSZ - MAXALIGN(SizeOfPageHeaderData + sizeof(ItemIdData)))
#define MinHeapTupleSize  MAXALIGN(SizeofHeapTupleHeader)

/*
 * MaxHeapTuplesPerPage 是可以装入一个堆页面的元组数量的上限。
 * （请注意，索引可以有更多，因为它们使用较小的元组头。）
 * 我们之所以得出这个除数是因为每个元组必须进行最大对齐，
 * 并且它必须有一个相应的行指针。
 *
 * 注意：使用 HOT 的时候，理论上在一个堆页面上可能会有更多的行指针
 * （而不是实际的元组）。然而我们仍然将行指针的数量限制在这个，
 * 以避免行指针的过度膨胀，并且不需要增加工作数组的大小。
 */
#define MaxHeapTuplesPerPage	\
	((int) ((BLCKSZ - SizeOfPageHeaderData) / \
			(MAXALIGN(SizeofHeapTupleHeader) + sizeof(ItemIdData))))

/*
 * MaxAttrSize 是对声明的 char(n) 和类似类型数据字段大小的
 * 一种有些任意的上限。它不必与 varlena 值的 *实际* 上限
 * 直接相关，当前为 1Gb（参见 postgres.h 中的 TOAST 结构）。
 * 我将其设为 10Mb，这似乎是一个合理的数字 --- tgl 2000年8月6日。
 */
#define MaxAttrSize		(10 * 1024 * 1024)



/*
 * MinimalTuple 是一种替代表示法，用于执行器内部的瞬态元组，在不需要事务状态信息的地方，元组行类型是已知的，并且由于我们需要存储许多元组，节省几个字节是值得的。选择这样的表示法，以便元组访问例程可以通过 HeapTupleData 指针结构同时处理完整或最小元组。访问例程之间没有区别，除了它们不能访问事务状态或 t_ctid 字段，因为这些字段并不存在。
 *
 * 在大多数情况下，MinimalTuples 应通过 TupleTableSlot 例程访问。这些例程将防止访问“系统列”，从而防止意外使用不存在的字段。
 *
 * MinimalTupleData 包含一个长度字、一些填充和与 HeapTupleHeaderData 匹配的字段，从 t_infomask2 开始。填充的选择使得 offsetof(t_infomask2) 在两个结构中具有相同的 MAXIMUM_ALIGNOF 模数。这使得数据对齐规则在两种情况下等效。
 *
 * 当通过 HeapTupleData 指针访问最小元组时，t_data 被设置为指向最小元组实际开始之前的 MINIMAL_TUPLE_OFFSET 字节位置——也就是说，匹配最小元组数据的完整元组的开始位置。这个技巧使得结构看起来等效。
 *
 * 注意，t_hoff 的计算与完整元组相同，因此它包括 MINIMAL_TUPLE_OFFSET 距离。然而，t_len 并不包括这一点。
 *
 * MINIMAL_TUPLE_DATA_OFFSET 是指向长度字段之外的第一个有用（非填充）数据的偏移量。tuplesort.c 和 tuplestore.c 使用此方法避免将填充写入磁盘。
 */
#define MINIMAL_TUPLE_OFFSET \
	((offsetof(HeapTupleHeaderData, t_infomask2) - sizeof(uint32)) / MAXIMUM_ALIGNOF * MAXIMUM_ALIGNOF)
#define MINIMAL_TUPLE_PADDING \
	((offsetof(HeapTupleHeaderData, t_infomask2) - sizeof(uint32)) % MAXIMUM_ALIGNOF)
#define MINIMAL_TUPLE_DATA_OFFSET \
	offsetof(MinimalTupleData, t_infomask2)

struct MinimalTupleData
{
	uint32		t_len;			/* 最小元组的实际长度 */

	char		mt_padding[MINIMAL_TUPLE_PADDING];

	/* 以下字段必须匹配HeapTupleHeaderData！ */

	uint16		t_infomask2;	/* 属性数量 + 各种标志 */

	uint16		t_infomask;		/* 各种标志位，见下文 */

	uint8		t_hoff;			/* 包含位图和填充的头部大小 */

	/* ^ - 23 字节 - ^ */

	bits8		t_bits[FLEXIBLE_ARRAY_MEMBER];	/* NULLs 的位图 */

	/* 结构结尾还跟随更多数据 */
};

/* typedef 出现在 htup.h 中 */

#define SizeofMinimalTupleHeader offsetof(MinimalTupleData, t_bits)


/*
 * GETSTRUCT - 给定一个HeapTuple指针，返回用户数据的地址
 */
#define GETSTRUCT(TUP) ((char *) ((TUP)->t_data) + (TUP)->t_data->t_hoff)

/*
 * 访问器宏用于 HeapTuple 指针。
 */

#define HeapTupleHasNulls(tuple) \
		(((tuple)->t_data->t_infomask & HEAP_HASNULL) != 0)

#define HeapTupleNoNulls(tuple) \
		(!((tuple)->t_data->t_infomask & HEAP_HASNULL))

#define HeapTupleHasVarWidth(tuple) \
		(((tuple)->t_data->t_infomask & HEAP_HASVARWIDTH) != 0)

#define HeapTupleAllFixed(tuple) \
		(!((tuple)->t_data->t_infomask & HEAP_HASVARWIDTH))

#define HeapTupleHasExternal(tuple) \
		(((tuple)->t_data->t_infomask & HEAP_HASEXTERNAL) != 0)

#define HeapTupleIsHotUpdated(tuple) \
		HeapTupleHeaderIsHotUpdated((tuple)->t_data)

#define HeapTupleSetHotUpdated(tuple) \
		HeapTupleHeaderSetHotUpdated((tuple)->t_data)

#define HeapTupleClearHotUpdated(tuple) \
		HeapTupleHeaderClearHotUpdated((tuple)->t_data)

#define HeapTupleIsHeapOnly(tuple) \
		HeapTupleHeaderIsHeapOnly((tuple)->t_data)

#define HeapTupleSetHeapOnly(tuple) \
		HeapTupleHeaderSetHeapOnly((tuple)->t_data)

#define HeapTupleClearHeapOnly(tuple) \
		HeapTupleHeaderClearHeapOnly((tuple)->t_data)

/* common/heaptuple.c 中的函数原型 */
extern Size heap_compute_data_size(TupleDesc tupleDesc,
								   Datum *values, bool *isnull);
extern void heap_fill_tuple(TupleDesc tupleDesc,
							Datum *values, bool *isnull,
							char *data, Size data_size,
							uint16 *infomask, bits8 *bit);
extern bool heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc);
extern Datum nocachegetattr(HeapTuple tup, int attnum,
							TupleDesc att);
extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
							 bool *isnull);
extern Datum getmissingattr(TupleDesc tupleDesc,
							int attnum, bool *isnull);
extern HeapTuple heap_copytuple(HeapTuple tuple);
extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
extern Datum heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc);
extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
								 Datum *values, bool *isnull);
extern HeapTuple heap_modify_tuple(HeapTuple tuple,
								   TupleDesc tupleDesc,
								   Datum *replValues,
								   bool *replIsnull,
								   bool *doReplace);
extern HeapTuple heap_modify_tuple_by_cols(HeapTuple tuple,
										   TupleDesc tupleDesc,
										   int nCols,
										   int *replCols,
										   Datum *replValues,
										   bool *replIsnull);
extern void heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
							  Datum *values, bool *isnull);
extern void heap_freetuple(HeapTuple htup);
extern MinimalTuple heap_form_minimal_tuple(TupleDesc tupleDescriptor,
											Datum *values, bool *isnull);
extern void heap_free_minimal_tuple(MinimalTuple mtup);
extern MinimalTuple heap_copy_minimal_tuple(MinimalTuple mtup);
extern HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple mtup);
extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup);
extern size_t varsize_any(void *p);
extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);

#ifndef FRONTEND
/*
 *	fastgetattr
 *		以 Datum 的形式获取用户属性的值（可能是值，也可能是元组数据区的指针）。
 *
 *		当可能请求系统属性时，不能使用此方法。
 *		此外，传入的 attnum 必须有效。如果有疑问，请使用 heap_getattr()。
 *
 *		此方法调用频繁，因此我们将可缓存和 NULL 查询宏化，对于其他情况调用 nocachegetattr()。
 */
static inline Datum
fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
{
	Assert(attnum > 0);

	*isnull = false;
	if (HeapTupleNoNulls(tup))
	{
		Form_pg_attribute att;

		att = TupleDescAttr(tupleDesc, attnum - 1);
		if (att->attcacheoff >= 0)
			return fetchatt(att, (char *) tup->t_data + tup->t_data->t_hoff +
							att->attcacheoff);
		else
			return nocachegetattr(tup, attnum, tupleDesc);
	}
	else
	{
		if (att_isnull(attnum - 1, tup->t_data->t_bits))
		{
			*isnull = true;
			return (Datum) NULL;
		}
		else
			return nocachegetattr(tup, attnum, tupleDesc);
	}
}

/*
 *	heap_getattr
 *		提取一个堆元组的属性并将其作为 Datum 返回。
 *		此方法适用于系统属性或用户属性。给定的 attnum 经过了适当的范围检查。
 *
 *		如果相关字段的值为 NULL，我们返回一个零 Datum 并设置 *isnull == true。否则，我们设置 *isnull == false。
 *
 *		<tup> 是指向堆元组的指针。<attnum> 是调用者想要的列（字段）的属性编号。<tupleDesc> 是描述行及其所有字段的结构的指针。
 *
 */
static inline Datum
heap_getattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
{
	if (attnum > 0)
	{
		if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data))
			return getmissingattr(tupleDesc, attnum, isnull);
		else
			return fastgetattr(tup, attnum, tupleDesc, isnull);
	}
	else
		return heap_getsysattr(tup, attnum, tupleDesc, isnull);
}
#endif							/* FRONTEND */

#endif							/* HTUP_DETAILS_H */
